/*
 * intController.c
 *
 * Author: Tarl Hahn
 * Version: 1.0
 * 
 * Header file simulating and interrupt controller.
 * The controller consumes IO requests for processing by IO devices and produces 
 * interrupts when the devices are completed, includes system timer
 *
 * Interrupt controller should be a new thread communicating with the CPU - a seperate thread -
 * This will provide an easy way to approximate the asycronicity between the CPU and all the  
 * different possible interrupts that could happen at any time.
 *
 */
#include <pthread.h> 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>

#include "intCtrl.h"
#include "global.h"

#define EMPTY_SLEEP 100000
 
// prototypes
DevicePtr* loadDevices(intCtrlPtr this, int* deviceArray, int arraylen);
int intCtrlDestruct(intCtrlPtr this);
void* runThread(void* arg);
char* ctrlToString(intCtrlPtr this, int* error);

// Initialize interrupt controller
intCtrlPtr intCtrl_init(SharedMemPtr mem, int* deviceArray, int len) 
{	// set up controller
	intCtrlPtr controller = (intCtrlPtr) malloc(sizeof(intCtrl));
	controller->timer = deviceConstruct(0, TIMER);    // system timer.
	controller->devices = (DevicePtr*) malloc(sizeof(DevicePtr*) * len); // an array of devices.
	controller->arraylen = len;
	controller->mem = mem;
	controller->timer->requestService(controller->timer, 1000, mem);
	// load the devices
	int i = 0;
	int j = 0; // loop thru all the network devices
	int last = -1;
	while(i < len)
	{
		j++;
		if(deviceArray[i] != last)
		{
			j = 1;
		}
		last = deviceArray[i];
		controller->devices[i] = deviceConstruct(j, deviceArray[i]);
		i++;
	}
	
	// mutex lock for shared memory area
	pthread_mutex_init(&controller->lock, NULL); 
	
	// member functions
	controller->destruct = intCtrlDestruct;
	controller->toString = ctrlToString;
	
	// run thread
	pthread_create(&controller->thread, NULL, runThread, controller); // runs thread for Timer
	
	return controller;
}

// destruct intCtrlr
int intCtrlDestruct(intCtrlPtr this)
{
	// check for null pointers
	if (this == (intCtrlPtr) NULL){ return NULL_PTR;}
	
	// shut off timer
	this->mem->run_flag = 0;
	int i;
	// join threads
	for(i = 0; i < this->arraylen; i++)
	{
		pthread_join(this->devices[i]->device_thread, NULL);
	}
	usleep(1000); // pause to allow threads to finish
	
	// destroy devices.
	for(i = 0; i < this->arraylen; i++)
	{
		this->devices[i]->destruct(this->devices[i]);
	}
	// free timer
	this->timer->destruct(this->timer);
	// free array
	free(this->devices);
	free(this);
	return NO_ERROR;
}

// find free device to work on
int request(intCtrlPtr ctrlr, int pid, int type)
{
	int i;
	for(i = 0; i < ctrlr->arraylen; i++)
	{
		if(ctrlr->devices[i]->type == type && ctrlr->devices[i]->pid == NO_PROCESS)
		{
			// assign process to the free device.
			ctrlr->devices[i]->pid = pid;
			// run thread.
			ctrlr->devices[i]->requestService(ctrlr->devices[i], 10, ctrlr->mem);
			return NO_ERROR;
		}
	}
	return DEVICE_UNAVAIL;
}

// thread run function
void* runThread(void* arg)
{
	// cast arg for useful work
	intCtrlPtr controller = (intCtrlPtr) arg;
	// initialize mutex lock
	pthread_mutex_t lock;
	pthread_mutex_init(&lock, NULL);
	
	int data[2]; // holds pid and device type
	int error = 0;
	int shutdown = 0;
	int sleep = 0;
	// continue running until run_flag is set to off.
	while(controller->mem->run_flag)
	{	// sleep until a request is generated
		while(controller->mem->getRequest(controller->mem, &lock, data) == EMPTY && shutdown < 10)
		{
			usleep(EMPTY_SLEEP);
			shutdown++;
		}
		error++;
		if (shutdown == 10)
		{	
			shutdown = 0;
			continue;
		}
		shutdown = 0;
		// if the requested device is unavailable
		if(DEVICE_UNAVAIL == request(controller, data[0], data[1]))
		{
			sleep++;
			// put request back in request queue
			controller->mem->addRequest(controller->mem, data[0], data[1], &lock);
		} 
		
		if (sleep == 10)
		{	
			sleep = 0;
			usleep(EMPTY_SLEEP);
		}
		
	}
	
	// close thread
	pthread_exit(0);
}

// returns a string rep of the controller
char* ctrlToString(intCtrlPtr this, int* error)
{
	// check for null pointers
	if (this == (intCtrlPtr) NULL)
	{ 
		*error = NULL_PTR;
		return 0;
	}	
	
	// intialize string vars
	char* string = (char*) malloc(sizeof(char) * this->arraylen * 20 + 25);
	char* dev_str =(char*) malloc(sizeof(char) * 15);
	// copy header
	strcpy(string, "Dev->run: ");
	int i;
	char type;
	for(i = 0; i < this->arraylen; i++)
	{
		// if the device is running print it in the running list
		if (this->devices[i] != (DevicePtr) NULL && this->devices[i]->pid != NO_PROCESS)
		{
			switch(this->devices[i]->type)
			{
				case NETWORK:
					type = 'N';
					break;
				case DISK_IO:
					type = 'D';
					break;
				case VIDEO:
					type = 'V';
					break;
				case MOUSE:
					type = 'M';
					break;
				case KEYBOARD:
					type = 'K';
			}
			snprintf(dev_str, 15, "%c%d: P%d", type, this->devices[i]->did, this->devices[i]->pid);
			strcat(string, dev_str);
			if (i != this->arraylen-1)
			{	// add semicolon is necessary
				strcat(string, ", ");
			}
		}
	}

	// set up for the available list
	strcat(string, "\nDev->avail: ");
	for(i = 0; i < this->arraylen; i++)
	{	// if the device is available add to list
		
		if (this->devices[i] != (DevicePtr) NULL && this->devices[i]->pid == NO_PROCESS)
		{	
			switch(this->devices[i]->type)
			{
				case NETWORK:
					type = 'N';
					break;
				case DISK_IO:
					type = 'D';
					break;
				case VIDEO:
					type = 'V';
					break;
				case MOUSE:
					type = 'M';
					break;
				case KEYBOARD:
					type = 'K';
			}
			// get the available device string
			snprintf(dev_str, 15, "%c%d", type, this->devices[i]->did);
			strcat(string, dev_str);
			if (i != this->arraylen-1)
			{	// add the semicolon
				strcat(string, ", ");
			}
			
		} 
		
	}
	// free temp string
	free(dev_str);
	// return
	return string;
}
/*
int main(void)
{
	int error = 0;
	SharedMemPtr mem = SharedMemConstruct();
	// load memory with requests
	pthread_mutex_t lock;
	pthread_mutex_init(&lock, NULL);
	for(error = 0; error < 3; error++)
	{
		mem->addRequest(mem, error,1, &lock);
	}
	
	while(error < 6)
	{
		mem->addRequest(mem, error,1, &lock);
		error++;
	}
	
	while(error < 10)
	{
		mem->addRequest(mem, error,2, &lock);
		error++;
	}
	
	while(error < 14)
	{
		mem->addRequest(mem, error,2, &lock);
		error++;
	}
	
	printf(mem->toStringReq(mem, &error));
	
	int deviceArray[] = {1, 2, 3, 5};
	intCtrlPtr ctrl = intCtrl_init(mem, &deviceArray[0], 4); 

	printf(ctrl->toString(ctrl, &error));
	
	pthread_join(ctrl->thread, NULL);
	printf(mem->toStringInt(mem, &error));
	int array[2];
	for(error = 0; error < 14; error++)
	{
		mem->getInterrupt(mem, &lock, &array[0]);
		printf("Interrupt returned pid %d, Type %d\n", array[0], array[1]);
	}
}*/

